import datetime
from typing import Union

from flask_sqlalchemy import SQLAlchemy, BaseQuery
from flask_sqlalchemy.model import DefaultMeta
from sqlalchemy import func, or_
from sqlalchemy.orm.collections import InstrumentedList

from cc_flask.exception import NotFound


db = SQLAlchemy()


class CQuery(BaseQuery):
    _with_deleted = False

    def __new__(cls, *args, **kwargs):
        obj = super(CQuery, cls).__new__(cls)
        if not hasattr(obj, "delete_time"):
            return obj

        obj._with_deleted = kwargs.pop('_with_deleted', False)
        if len(args) > 0:
            super(CQuery, obj).__init__(*args, **kwargs)
            return obj.filter_by(delete_time=None) if not obj._with_deleted else obj
        return obj

    def __init__(self, *args, **kwargs):
        pass

    def with_deleted(self):
        return self.__class__(
            self._only_full_mapper_zero('get'),
            session=db.session(),
            _with_deleted=True
        )

    def _get(self, *args, **kwargs):
        print(self)
        return super(CQuery, self).get(*args, **kwargs)

    def get_all(self, *args, **kwargs):
        """ 获取一个对象，不排除软删除 """
        return self.with_deleted()._get(*args, **kwargs)

    def get(self, *args, **kwargs):
        """ 获取一个对象，排除软删除 """
        obj = self.with_deleted()._get(*args, **kwargs)
        # 排除软删除并且已软删除，返回None
        if not self._with_deleted and obj.delete_time is not None:
            return None
        return obj

    def filter_all(self, *args, **kwargs):
        """ 过滤对象，不排除软删除项 """
        return super(CQuery, self).filter(*args, **kwargs)

    def delete(self):
        """ 逻辑删除 """
        self.update({CModel.delete_time: func.now()})

    def restore(self, synchronize_session=True):
        """ 逻辑删除后恢复 """
        self.update({CModel.delete_time: None}, synchronize_session=synchronize_session)

    def hard_delete(self, synchronize_session='evaluate'):
        """ 物理删除 """
        super(CQuery, self).delete(synchronize_session=synchronize_session)

    def get_or_404(self, ident, description=None):
        rv = self.get(ident)
        if rv is None:
            raise NotFound("在数据库中找不到对象", data={'id': ident})
        return rv

    def first_or_404(self, description=None):

        rv = self.first()
        if rv is None:
            raise NotFound(description or "在数据库中找不到对象")
        return rv


db.query_class = CQuery


def Integer(comment=None, nullable=False, primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义定义int类型
    :param primary_key: 是否key
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param comment: 数据库注释
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    return db.Column(
        db.Integer,
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        **kwargs
    )


def String(comment=None, nullable=False, max_length=255, primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义字符串类型
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param max_length: 长度，默认255
    :param primary_key: 是否key
    :param comment: 数据库注释
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    return db.Column(
        db.String(max_length),
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        **kwargs
    )


def Boolean(comment=None, nullable=False, primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义布尔类型
    :param comment: 数据库注释
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param primary_key: 是否key
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    return db.Column(
        db.Boolean,
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        **kwargs
    )


def DateTime(comment=None, nullable=False, auto_now=False, auto=False,
             primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义时间类型
    :param comment: 数据库注释
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param auto_now: True: 创建时是否自动填充现在的时间，只在创建时更新。
    :param auto: True: 是否自动更新时间，创建时和每次update都会自动更新
    :param primary_key: 是否key
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    if auto_now or auto:
        default = func.now()

    return db.Column(
        db.DateTime,
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        onupdate=func.now() if auto else None,
        **kwargs
    )


def Date(comment=None, nullable=False, auto_now=False, auto=False,
         primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义日期类型
    :param primary_key: 是否key
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param auto_now: True: 创建时是否自动填充现在的时间，只在创建时更新。
    :param auto: True: 是否自动更新时间，创建时和每次update都会自动更新
    :param comment: 数据库注释
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    if auto_now or auto:
        default = func.now()

    return db.Column(
        db.Date,
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        onupdate=func.now() if auto else None,
        **kwargs
    )


def Time(comment=None, nullable=False, auto_now=False, auto=False,
         primary_key=False, doc=None, default=None, **kwargs):
    """
    自定义日期类型
    :param primary_key: 是否key
    :param nullable: 字段是否可空，默认是不可空，True:不可为null（True就是加上 not null）
    :param auto_now: True: 创建时是否自动填充现在的时间，只在创建时更新。
    :param auto: True: 是否自动更新时间，创建时和每次update都会自动更新
    :param comment: 数据库注释
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return:
    """
    if auto_now or auto:
        default = func.now()

    return db.Column(
        db.Date,
        primary_key=primary_key,
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        onupdate=func.now() if auto else None,
        **kwargs
    )


def ForeignKey(to: Union[db.Model, str], backref: str, comment: str = None, to_table: str = None,
               nullable: bool = False, doc: str = None, default=None, **kwargs):
    """
    自定义外键
    :param to: 关联的表，Model对象或str类型（即类名，如果是str类型，to_table_name必填），
    :param backref: 外键，用于外键关系，如果不设置，默认为[类名]_set，如：User类中定义了ForeignKey字段，则backref=user_set
    :param comment: 数据库注释
    :param to_table: 关键的表的表名（即__tablename__），to是str类型时必填
    :param nullable: 字段是否可空，默认是不可空，True:可空，False：不可空(not null)
    :param doc: python文档的注释（与数据库无关）
    :param default: 默认值
    :param kwargs: 其他参数
    :return: 返回一个元组（两个参数），第一个参数是关联表的key，第二个参数是关系字段relationship。
             示例：user_id, user = ForeignKey(...)
    """
    # to_table_name = type(to()).__name__.lower()

    if isinstance(to, DefaultMeta):
        to_table = to.__tablename__
    elif isinstance(to, str):
        if to_table is None:
            raise ValueError("to传入了str类型，to_table必填。（提示：to_table传入外键表的表名）")
    else:
        raise TypeError("to字段类型错误，支持类型：flask_sqlalchemy.model.Model 和 str")

    # if not backref:
    #     backref = inspect.getframeinfo(inspect.currentframe().f_back)[2].lower()
        # backref = traceback.extract_stack()[-2][2].lower()  # 这个方法也能实现

    return db.Column(
        db.Integer,
        db.ForeignKey("%s.id" % to_table),
        nullable=nullable,
        doc=doc or comment,
        comment=comment,
        default=default,
        **kwargs
    ), db.relationship(to, backref=backref)


def ManyToMany(to: Union[db.Model, str], table_name: str, comment: str = None, to_table=None, secondary=None,
               backref: str = None, doc: str = None, **kwargs):
    """
    自定义外键
    :param to: 关联的表，Model对象或str类型（即类名，如果是str类型，to_table_name必填），
    :param table_name: 当前表的表名（__tablename__），
    :param to_table: 关键的表的表名（即__tablename__），to是str类型时必填
    :param comment: 数据库注释
    :param secondary: 中间表
    :param backref: 外键，用于外键关系，如果不设置，默认为[类名]_set，如：User类中定义了ManyToMany字段，则backref=user_set
    :param doc: python文档的注释（与数据库无关）
    :param kwargs: 其他参数
    :return: 返回两个参数，ForeignKey, relationship。示例：user_id, user = ForeignKey(...)
    """

    if isinstance(to, DefaultMeta):
        to_table = to.__tablename__
    elif isinstance(to, str):
        if to_table is None:
            raise ValueError("to传入了str类型，to_table必填。（提示：to_table传入外键表的表名）")
    else:
        raise TypeError("to字段类型错误，支持类型：flask_sqlalchemy.model.Model 和 str")
    # table = inspect.getframeinfo(inspect.currentframe().f_back)[2]
    # table = traceback.extract_stack()[-2][2].lower()  # 这个方法也能实现

    if secondary is None:
        secondary = db.Table(
            "%s_%s" % (table_name, to_table),
            db.metadata,
            db.Column('%s_id' % table_name, db.ForeignKey('%s.id' % table_name), comment=comment, primary_key=True),
            db.Column('%s_id' % to_table, db.ForeignKey('%s.id' % to_table), comment=comment, primary_key=True),
        )
    return db.relationship(to, secondary=secondary, backref=backref or ("%s_set" % table_name), **kwargs)
    # return db.relationship(
    #     to, secondary=secondary, backref=db.backref(backref or ("%s_set" % table_name), lazy='joined'), **kwargs)


class CModel(db.Model):
    __abstract__ = True

    id = Integer('id', primary_key=True, autoincrement=True, name='id')
    create_time = DateTime('创建时间', nullable=True, auto_now=True, name='create_time')
    update_time = DateTime('更新时间', nullable=True, auto=True, name='update_time')
    delete_time = DateTime('删除时间', nullable=True, name='delete_time')

    default = []
    detail = []

    # def __init__(self, *args, **kwargs):
    #     super(Model, self).__init__(*args, **kwargs)
    #     if not self.__tablename__:
    #         self.__tablename__ = self.__class__.__name__.lower()

    def get_default(self):
        return self.get_field(self.default)

    def get_detail(self):
        return self.get_field(self.detail)

    def get_field(self, fields: list):
        """
        模型字段解析
        :param fields: 字段列表，列表里可以是字符串，也可以是元组
            字符串：models字段名，输出响应的类型，
            元组：如（'group', 'detail'）
                参数1：字段名
                参数2：根据参数1的字段类型，可以有不同的解析方式
                    1、外键（ForeignKey、ManyToMany）：
                        示例：('group', 'detail')
                        可选值（str类型）：'detail'、'default'
                        'detail'：解析外键字段时，使用detail的规则解析（外键表未定义detail/default时，返回{}）
                        'default'：解析外键字段时，使用default的规则解析（外键表未定义detail/default时，返回{}）

                    2、基础类型（String、Int、Float、Bool）：指定转换类型，如果转换失败（即不符合转换规则），将原样返回。
                        示例：('id', str)
                        可选值（对象类型）：int、float、bool、str
                        int：指定转换int类型
                        float：指定转换float类型
                        bool：指定转换bool类型
                        str: 指定转换str类型

                    3、日期时间(DateTime、Data、Time)：格式化输出字符串
                        示例：('created', '%Y-%m-%d %H:%M:%S')
                        默认格式为"%Y-%m-%d %H:%M:%S"

        :return: 根据提供的字段返回一个字典
        """
        data = {}
        for f_name in fields:
            _tow = None  # 元组第二个参数，如果是外键，它就是外键的detail、default，如果是datetime,它就是时间格式
            if isinstance(f_name, (tuple, list)):
                # 处理元组类型
                _tow = f_name[1]
                f_name = f_name[0]

            _field = getattr(self, f_name)  # 字段值

            _data = None
            if isinstance(_field, (str, int, bool, float)):  # 字符串类型、int类型
                if _tow:
                    try:
                        _field = _tow(_field)
                    except:
                        pass
                _data = _field
            elif isinstance(_field, (datetime.datetime, datetime.date, datetime.time)):  # 时间类型
                _format = _tow or "%Y-%m-%d %H:%M:%S"
                _data = _field.strftime(_tow)
            elif isinstance(_field, CModel):  # 一对多类型
                if _tow is None:
                    _tow = 'default'
                # _data = _field.get_field(_field.detail) if _tow == 'detail' else _field.get_field(_field.default)
                _data = _field.get_field(getattr(_field, _tow))

            elif isinstance(_field, InstrumentedList):  # 多对多类型
                if _tow is None:
                    _tow = 'default'

                _data = []
                for i in _field:
                    print('--', i)
                    if isinstance(i, CModel):
                        _data.append(i.get_field(getattr(i, _tow)))

            data[f_name] = _data

        return data
